AQS CAS简单详解(写的比较好) - CSDN博客

创建时间:2018/7/27 13:39
来源:https://blog.csdn.net/zcw4237256/article/details/78552741

AQS CAS简单详解(写的比较好)

2017年11月16日 16:31:01
阅读数:5485

CAS(Compare And Swap)

什么是CAS

CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

在JAVA中,sun.misc.Unsafe 类提供了硬件级别的原子操作来实现这个CAS。 java.util.concurrent 包下的大量类都使用了这个 Unsafe.java 类的CAS操作。至于 Unsafe.java 的具体实现这里就不讨论了。

CAS典型应用

java.util.concurrent.atomic 包下的类大多是使用CAS操作来实现的(eg. AtomicInteger.java,AtomicBoolean,AtomicLong)。下面以 AtomicInteger.java 的部分实现来大致讲解下这些原子类的实现。

  1. 1
    public class AtomicInteger extends Number implements java.io.Serializable {
  2. 2
    private static final long serialVersionUID = 6214790243416807050L;
  3. 3
  4. 4
    // setup to use Unsafe.compareAndSwapInt for updates
  5. 5
    private static final Unsafe unsafe = Unsafe.getUnsafe();
  6. 6
  7. 7
    private volatile int value;// 初始int大小
  8. 8
    // 省略了部分代码...
  9. 9
  10. 10
    // 带参数构造函数,可设置初始int大小
  11. 11
    public AtomicInteger(int initialValue) {
  12. 12
    value = initialValue;
  13. 13
    }
  14. 14
    // 不带参数构造函数,初始int大小为0
  15. 15
    public AtomicInteger() {
  16. 16
    }
  17. 17
  18. 18
    // 获取当前值
  19. 19
    public final int get() {
  20. 20
    return value;
  21. 21
    }
  22. 22
  23. 23
    // 设置值为 newValue
  24. 24
    public final void set(int newValue) {
  25. 25
    value = newValue;
  26. 26
    }
  27. 27
  28. 28
    //返回旧值,并设置新值为 newValue
  29. 29
    public final int getAndSet(int newValue) {
  30. 30
    /**
  31. 31
    * 这里使用for循环不断通过CAS操作来设置新值
  32. 32
    * CAS实现和加锁实现的关系有点类似乐观锁和悲观锁的关系
  33. 33
    * */
  34. 34
    for (;;) {
  35. 35
    int current = get();
  36. 36
    if (compareAndSet(current, newValue))
  37. 37
    return current;
  38. 38
    }
  39. 39
    }
  40. 40
  41. 41
    // 原子的设置新值为update, expect为期望的当前的值
  42. 42
    public final boolean compareAndSet(int expect, int update) {
  43. 43
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
  44. 44
    }
  45. 45
  46. 46
    // 获取当前值current,并设置新值为current+1
  47. 47
    public final int getAndIncrement() {
  48. 48
    for (;;) {
  49. 49
    int current = get();
  50. 50
    int next = current + 1;
  51. 51
    if (compareAndSet(current, next))
  52. 52
    return current;
  53. 53
    }
  54. 54
    }
  55. 55
  56. 56
    // 此处省略部分代码,余下的代码大致实现原理都是类似的
  57. 57
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

一般来说在竞争不是特别激烈的时候,使用该包下的原子操作性能比使用 synchronized 关键字的方式高效的多(查看getAndSet(),可知如果资源竞争十分激烈的话,这个for循环可能换持续很久都不能成功跳出。不过这种情况可能需要考虑降低资源竞争才是)。 
在较多的场景我们都可能会使用到这些原子类操作。一个典型应用就是计数了,在多线程的情况下需要考虑线程安全问题。通常第一映像可能就是:

  1. 1
    public class Counter {
  2. 2
    private int count;
  3. 3
    public Counter(){}
  4. 4
    public int getCount(){
  5. 5
    return count;
  6. 6
    }
  7. 7
    public void increase(){
  8. 8
    count++;
  9. 9
    }
  10. 10
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上面这个类在多线程环境下会有线程安全问题,要解决这个问题最简单的方式可能就是通过加锁的方式,调整如下:

  1. 1
    public class Counter {
  2. 2
    private int count;
  3. 3
    public Counter(){}
  4. 4
    public synchronized int getCount(){
  5. 5
    return count;
  6. 6
    }
  7. 7
    public synchronized void increase(){
  8. 8
    count++;
  9. 9
    }
  10. 10
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这类似于悲观锁的实现,我需要获取这个资源,那么我就给他加锁,别的线程都无法访问该资源,直到我操作完后释放对该资源的锁。我们知道,悲观锁的效率是不如乐观锁的,上面说了Atomic下的原子类的实现是类似乐观锁的,效率会比使用 synchronized 关系字高,推荐使用这种方式,实现如下:

  1. 1
    public class Counter {
  2. 2
    private AtomicInteger count = new AtomicInteger();
  3. 3
    public Counter(){}
  4. 4
    public int getCount(){
  5. 5
    return count.get();
  6. 6
    }
  7. 7
    public void increase(){
  8. 8
    count.getAndIncrement();
  9. 9
    }
  10. 10
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

AQS(AbstractQueuedSynchronizer)

什么是AQS

AQS(AbstractQueuedSynchronizer),AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。如果你有看过类似 CountDownLatch 类的源码实现,会发现其内部有一个继承了 AbstractQueuedSynchronizer 的内部类 Sync。可见 CountDownLatch 是基于AQS框架来实现的一个同步器.类似的同步器在JUC下还有不少。(eg. Semaphore)

AQS用法

如上所述,AQS管理一个关于状态信息的单一整数,该整数可以表现任何状态。比如, Semaphore 用它来表现剩余的许可数,ReentrantLock 用它来表现拥有它的线程已经请求了多少次锁;FutureTask 用它来表现任务的状态(尚未开始、运行、完成和取消)

  1. 1
    To use this class as the basis of a synchronizer, redefine the
  2. 2
    * following methods, as applicable, by inspecting and/or modifying
  3. 3
    * the synchronization state using {@link #getState}, {@link
  4. 4
    * #setState} and/or {@link #compareAndSetState}:
  5. 5
    *
  6. 6
    * <ul>
  7. 7
    * <li> {@link #tryAcquire}
  8. 8
    * <li> {@link #tryRelease}
  9. 9
    * <li> {@link #tryAcquireShared}
  10. 10
    * <li> {@link #tryReleaseShared}
  11. 11
    * <li> {@link #isHeldExclusively}
  12. 12
    * </ul>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如JDK的文档中所说,使用AQS来实现一个同步器需要覆盖实现如下几个方法,并且使用getState,setState,compareAndSetState这几个方法来设置获取状态 
1. boolean tryAcquire(int arg) 
2. boolean tryRelease(int arg) 
3. int tryAcquireShared(int arg) 
4. boolean tryReleaseShared(int arg) 
5. boolean isHeldExclusively()

以上方法不需要全部实现,根据获取的锁的种类可以选择实现不同的方法,支持独占(排他)获取锁的同步器应该实现tryAcquire、 tryReleaseisHeldExclusively而支持共享获取的同步器应该实现tryAcquireSharedtryReleaseSharedisHeldExclusively。下面以 CountDownLatch 举例说明基于AQS实现同步器, CountDownLatch 用同步状态持有当前计数,countDown方法调用 release从而导致计数器递减;当计数器为0时,解除所有线程的等待;await调用acquire,如果计数器为0,acquire 会立即返回,否则阻塞。通常用于某任务需要等待其他任务都完成后才能继续执行的情景。源码如下:

  1. 1
    public class CountDownLatch {
  2. 2
    /**
  3. 3
    * 基于AQS的内部Sync
  4. 4
    * 使用AQS的state来表示计数count.
  5. 5
    */
  6. 6
    private static final class Sync extends AbstractQueuedSynchronizer {
  7. 7
    private static final long serialVersionUID = 4982264981922014374L;
  8. 8
  9. 9
    Sync(int count) {
  10. 10
    // 使用AQS的getState()方法设置状态
  11. 11
    setState(count);
  12. 12
    }
  13. 13
  14. 14
    int getCount() {
  15. 15
    // 使用AQS的getState()方法获取状态
  16. 16
    return getState();
  17. 17
    }
  18. 18
  19. 19
    // 覆盖在共享模式下尝试获取锁
  20. 20
    protected int tryAcquireShared(int acquires) {
  21. 21
    // 这里用状态state是否为0来表示是否成功,为0的时候可以获取到返回1,否则不可以返回-1
  22. 22
    return (getState() == 0) ? 1 : -1;
  23. 23
    }
  24. 24
  25. 25
    // 覆盖在共享模式下尝试释放锁
  26. 26
    protected boolean tryReleaseShared(int releases) {
  27. 27
    // 在for循环中Decrement count直至成功;
  28. 28
    // 当状态值即count为0的时候,返回false表示 signal when transition to zero
  29. 29
    for (;;) {
  30. 30
    int c = getState();
  31. 31
    if (c == 0)
  32. 32
    return false;
  33. 33
    int nextc = c-1;
  34. 34
    if (compareAndSetState(c, nextc))
  35. 35
    return nextc == 0;
  36. 36
    }
  37. 37
    }
  38. 38
    }
  39. 39
  40. 40
    private final Sync sync;
  41. 41
  42. 42
    // 使用给定计数值构造CountDownLatch
  43. 43
    public CountDownLatch(int count) {
  44. 44
    if (count < 0) throw new IllegalArgumentException("count < 0");
  45. 45
    this.sync = new Sync(count);
  46. 46
    }
  47. 47
  48. 48
    // 让当前线程阻塞直到计数count变为0,或者线程被中断
  49. 49
    public void await() throws InterruptedException {
  50. 50
    sync.acquireSharedInterruptibly(1);
  51. 51
    }
  52. 52
  53. 53
    // 阻塞当前线程,除非count变为0或者等待了timeout的时间。当count变为0时,返回true
  54. 54
    public boolean await(long timeout, TimeUnit unit)
  55. 55
    throws InterruptedException {
  56. 56
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
  57. 57
    }
  58. 58
  59. 59
    // count递减
  60. 60
    public void countDown() {
  61. 61
    sync.releaseShared(1);
  62. 62
    }
  63. 63
  64. 64
    // 获取当前count值
  65. 65
    public long getCount() {
  66. 66
    return sync.getCount();
  67. 67
    }
  68. 68
  69. 69
    public String toString() {
  70. 70
    return super.toString() + "[Count = " + sync.getCount() + "]";
  71. 71
    }
  72. 72
    }